<!DOCTYPE html>


<html lang="zh-CN">


<head>
  <meta charset="utf-8" />
    
  <meta name="description" content="welcome" />
  
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
  <title>
    服务网关Spring Cloud Gateway |  ChenyyのBlog
  </title>
  <meta name="generator" content="hexo-theme-ayer">
  
  <link rel="shortcut icon" href="/favicon.ico" />
  
  
<link rel="stylesheet" href="/dist/main.css">

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/Shen-Yu/cdn/css/remixicon.min.css">
  
<link rel="stylesheet" href="/css/custom.css">

  
  <script src="https://cdn.jsdelivr.net/npm/pace-js@1.0.2/pace.min.js"></script>
  
  

  

</head>

</html>

<body>
  <div id="app">
    
      
    <main class="content on">
      <section class="outer">
  <article
  id="post-framework/Spring Cloud Gateway"
  class="article article-type-post"
  itemscope
  itemprop="blogPost"
  data-scroll-reveal
>
  <div class="article-inner">
    
    <header class="article-header">
       
<h1 class="article-title sea-center" style="border-left:0" itemprop="name">
  服务网关Spring Cloud Gateway
</h1>
 

    </header>
     
    <div class="article-meta">
      <a href="/2021/06/21/framework/Spring%20Cloud%20Gateway/" class="article-date">
  <time datetime="2021-06-21T13:21:21.000Z" itemprop="datePublished">2021-06-21</time>
</a> 
  <div class="article-category">
    <a class="article-category-link" href="/categories/Spring-Cloud/">Spring Cloud</a>
  </div>
  
<div class="word_count">
    <span class="post-time">
        <span class="post-meta-item-icon">
            <i class="ri-quill-pen-line"></i>
            <span class="post-meta-item-text"> 字数统计:</span>
            <span class="post-count">7.6k</span>
        </span>
    </span>

    <span class="post-time">
        &nbsp; | &nbsp;
        <span class="post-meta-item-icon">
            <i class="ri-book-open-line"></i>
            <span class="post-meta-item-text"> 阅读时长≈</span>
            <span class="post-count">32 分钟</span>
        </span>
    </span>
</div>
 
    </div>
      
    <div class="tocbot"></div>




  
    <div class="article-entry" itemprop="articleBody">
       
  <ul>
<li>简介</li>
<li>predicate</li>
<li>filter</li>
<li>限流</li>
<li>配合注册中心路由转发</li>
<li>使用</li>
</ul>
<span id="more"></span>

<p>Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架，取代Zuul网关。网关作为流量的，在微服务系统中有着非常作用，网关常见的功能有路由转发、权限校验、限流控制等作用。</p>
<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><h2 id="作用"><a href="#作用" class="headerlink" title="作用"></a>作用</h2><ul>
<li>协议转换，路由转发</li>
<li>流量聚合，对流量进行监控，日志输出</li>
<li>作为整个系统的前端工程，对流量进行控制，有限流的作用</li>
<li>作为系统的前端边界，外部流量只能通过网关才能访问系统</li>
<li>可以在网关层做权限的判断</li>
<li>可以在网关层做缓存</li>
</ul>
<h2 id="工作流程"><a href="#工作流程" class="headerlink" title="工作流程"></a>工作流程</h2><p>客户端向Spring Cloud Gateway发出请求。 如果Gateway Handler Mapping确定请求与路由匹配（这个时候就用到predicate），则将其发送到Gateway web handler处理。 </p>
<p>Gateway web handler处理请求时会经过一系列的过滤器链。 过滤器链被虚线划分的原因是过滤器链可以在发送代理请求之前或之后执行过滤逻辑。 </p>
<p>先执行所有“pre”过滤器逻辑，然后进行代理请求。 在发出代理请求之后，收到代理服务的响应之后执行“post”过滤器逻辑。在执行所有“pre”过滤器逻辑时，往往进行了鉴权、限流、日志输出等功能，以及请求头的更改、协议的转换；转发之后收到响应之后，会执行所有“post”过滤器的逻辑，在这里可以响应数据进行了修改，比如响应头、协议的转换等。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621074953.png"></p>
<h1 id="predicate"><a href="#predicate" class="headerlink" title="predicate"></a>predicate</h1><h2 id="predicate简介"><a href="#predicate简介" class="headerlink" title="predicate简介"></a>predicate简介</h2><p>predicate（断言）的作用是将请求和路由进行匹配，它决定了一个请求走哪一个路由。</p>
<blockquote>
<p>Predicate来自于java8的接口。Predicate 接受一个输入参数，返回一个布尔值结果。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑（比如：与，或，非）。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。add–与、or–或、negate–非。</p>
</blockquote>
<p>Spring Cloud Gateway内置了许多Predict,这些Predict的源码在org.springframework.cloud.gateway.handler.predicate包中，列举各种Predicate如下图：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621075533.png"></p>
<p>上图中有很多类型的Predicate</p>
<ul>
<li>时间类型的Predicated（AfterRoutePredicateFactory BeforeRoutePredicateFactory BetweenRoutePredicateFactory），当只有满足特定时间要求的请求会进入到此predicate中，并交由router处理</li>
<li>cookie类型的CookieRoutePredicateFactory，指定的cookie满足正则匹配，才会进入此router</li>
<li>以及host、method、path、querparam、remoteaddr类型的predicate，每一种predicate都会对当前的客户端请求进行判断，是否满足当前的要求，如果满足则交给当前请求处理。</li>
<li>如果有很多个Predicate，并且一个请求满足多个Predicate，则按照配置的顺序第一个生效。</li>
</ul>
<h2 id="predicate实战"><a href="#predicate实战" class="headerlink" title="predicate实战"></a>predicate实战</h2><p>案例来源于官方文档：<a target="_blank" rel="noopener" href="http://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html">http://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html</a> </p>
<h3 id="Method-Route-Predicate-Factory"><a href="#Method-Route-Predicate-Factory" class="headerlink" title="Method Route Predicate Factory"></a>Method Route Predicate Factory</h3><p>Method Route Predicate Factory 需要一个参数，即请求的类型。比如GET类型的请求都转发到此路由。在工程的配置文件加上以下的配置：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  profiles:</span><br><span class="line">    active: method_route</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">spring:</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      routes:</span><br><span class="line">      - id: method_route</span><br><span class="line">        uri: http://httpbin.org:80/get</span><br><span class="line">        predicates:</span><br><span class="line">        - Method=GET</span><br><span class="line">  profiles: method_route</span><br></pre></td></tr></table></figure>

<p>在上面的配置中，所有的GET类型的请求都会路由转发到配置的uri。使用 curl命令模拟 get类型的请求，会得到正确的返回结果。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ curl localhost:8081</span><br></pre></td></tr></table></figure>

<p>使用 curl命令模拟 post请求，则返回404结果。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ curl -XPOST localhost:8081</span><br></pre></td></tr></table></figure>



<h2 id="源码下载"><a href="#源码下载" class="headerlink" title="源码下载"></a>源码下载</h2><p><a target="_blank" rel="noopener" href="https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-predicate">https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-predicate</a></p>
<h1 id="filter"><a href="#filter" class="headerlink" title="filter"></a>filter</h1><p>Predict决定了请求由哪一个路由处理，在路由处理之前，需要经过“pre”类型的过滤器处理，处理返回响应之后，可以由“post”类型的过滤器处理。</p>
<p>在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等，在“post”类型的过滤器中可以做响应内容、响应头的修改，日志的输出，流量监控等。</p>
<h2 id="作用-1"><a href="#作用-1" class="headerlink" title="作用"></a>作用</h2><p>当我们有很多个服务时，客户端请求各个服务的Api时，每个服务都需要做相同的事情，比如鉴权、限流、日志输出等。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621080244.png"></p>
<p>对于这样重复的工作，可以在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gatewat服务，然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用，外接的请求访问系统，必须先通过网关层。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621080230.png"></p>
<h2 id="生命周期"><a href="#生命周期" class="headerlink" title="生命周期"></a>生命周期</h2><p>客户端的请求先经过“pre”类型的filter，然后将请求转发到具体的业务服务，收到业务服务的响应之后，再经过“post”类型的filter处理，最后返回响应到客户端。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621080355.png"></p>
<p>filter除了分为“pre”和“post”两种方式的filter外，filter从作用范围可分为另外两种，一种是针对于单个路由的gateway filter，它在配置文件中的写法同predict类似；另外一种是针对于所有路由的global gateway filer。</p>
<h2 id="gateway-filter"><a href="#gateway-filter" class="headerlink" title="gateway filter"></a>gateway filter</h2><p>过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。</p>
<p>GatewayFilter工厂同上一篇介绍的Predicate工厂类似，都是在配置文件application.yml中配置。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。</p>
<p>遵循了约定大于配置的思想，只需要在配置文件配置GatewayFilter Factory的名称，而不需要写全部的类名，比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader，而不是全部类名。</p>
<p>Spring Cloud Gateway 内置的过滤器工厂一览表如下：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621080555.png"></p>
<h3 id="AddRequestHeader-GatewayFilter-Factory"><a href="#AddRequestHeader-GatewayFilter-Factory" class="headerlink" title="AddRequestHeader GatewayFilter Factory"></a>AddRequestHeader GatewayFilter Factory</h3><p>创建工程，引入相关的依赖,包括spring boot 版本2.0.5，spring Cloud版本Finchley，gateway依赖如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>在工程的配置文件中，加入以下的配置：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: 8081</span><br><span class="line">spring:</span><br><span class="line">  profiles:</span><br><span class="line">    active: add_request_header_route</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">spring:</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      routes:</span><br><span class="line">      - id: add_request_header_route</span><br><span class="line">        uri: http://httpbin.org:80/get</span><br><span class="line">        filters:</span><br><span class="line">        - AddRequestHeader=X-Request-Foo, Bar</span><br><span class="line">        predicates:</span><br><span class="line">        - After=2017-01-20T17:42:47.789-07:00[America/Denver]</span><br><span class="line">  profiles: add_request_header_route</span><br></pre></td></tr></table></figure>

<p>在add_request_header_route配置中，配置了路由地址为<a target="_blank" rel="noopener" href="http://httpbin.org/get%EF%BC%8C%E8%AF%A5router%E6%9C%89AfterPredictFactory%EF%BC%8C%E6%9C%89%E4%B8%80%E4%B8%AAfilter%E4%B8%BAAddRequestHeaderGatewayFilterFactory(%E7%BA%A6%E5%AE%9A%E5%86%99%E6%88%90AddRequestHeader)%E3%80%82">http://httpbin.org:80/get，该router有AfterPredictFactory，有一个filter为AddRequestHeaderGatewayFilterFactory(约定写成AddRequestHeader)。</a></p>
<p>AddRequestHeader过滤器工厂会在请求头加上一对请求头，名称为X-Request-Foo，值为Bar。</p>
<p>AddRequestHeaderGatewayFilterFactory的源码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory &#123;</span><br><span class="line"></span><br><span class="line">	@Override</span><br><span class="line">	public GatewayFilter apply(NameValueConfig config) &#123;</span><br><span class="line">		return (exchange, chain) -&gt; &#123;</span><br><span class="line">			ServerHttpRequest request = exchange.getRequest().mutate()</span><br><span class="line">					.header(config.getName(), config.getValue())</span><br><span class="line">					.build();</span><br><span class="line"></span><br><span class="line">			return chain.filter(exchange.mutate().request(request).build());</span><br><span class="line">		&#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>由上面的代码可知，根据旧的ServerHttpRequest创建新的 ServerHttpRequest ，在新的ServerHttpRequest加了一个请求头，然后创建新的 ServerWebExchange ，提交过滤器链继续过滤。</p>
<p>启动工程，通过curl命令来模拟请求：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl localhost:8081</span><br></pre></td></tr></table></figure>

<p>最终显示了从 <a target="_blank" rel="noopener" href="http://httpbin.org/get%E5%BE%97%E5%88%B0%E4%BA%86%E8%AF%B7%E6%B1%82%EF%BC%8C%E5%93%8D%E5%BA%94%E5%A6%82%E4%B8%8B%EF%BC%9A">http://httpbin.org:80/get得到了请求，响应如下：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;args&quot;: &#123;&#125;,</span><br><span class="line">  &quot;headers&quot;: &#123;</span><br><span class="line">    &quot;Accept&quot;: &quot;*/*&quot;,</span><br><span class="line">    &quot;Connection&quot;: &quot;close&quot;,</span><br><span class="line">    &quot;Forwarded&quot;: &quot;proto=http;host=\&quot;localhost:8081\&quot;;for=\&quot;0:0:0:0:0:0:0:1:56248\&quot;&quot;,</span><br><span class="line">    &quot;Host&quot;: &quot;httpbin.org&quot;,</span><br><span class="line">    &quot;User-Agent&quot;: &quot;curl/7.58.0&quot;,</span><br><span class="line">    &quot;X-Forwarded-Host&quot;: &quot;localhost:8081&quot;,</span><br><span class="line">    &quot;X-Request-Foo&quot;: &quot;Bar&quot;</span><br><span class="line">  &#125;,</span><br><span class="line">  &quot;origin&quot;: &quot;0:0:0:0:0:0:0:1, 210.22.21.66&quot;,</span><br><span class="line">  &quot;url&quot;: &quot;http://localhost:8081/get&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>从上面的响应可知，确实在请求头中加入了X-Request-Foo这样的一个请求头，在配置文件中配置的AddRequestHeader过滤器工厂生效。</p>
<h3 id="RewritePath-GatewayFilter-Factory"><a href="#RewritePath-GatewayFilter-Factory" class="headerlink" title="RewritePath GatewayFilter Factory"></a>RewritePath GatewayFilter Factory</h3><p>在Nginx服务启中有一个非常强大的功能就是重写路径，Spring Cloud Gateway默认也提供了这样的功能，这个功能是Zuul没有的。在配置文件中加上以下的配置：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  profiles:</span><br><span class="line">    active: rewritepath_route</span><br><span class="line">---</span><br><span class="line">spring:</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      routes:</span><br><span class="line">      - id: rewritepath_route</span><br><span class="line">        uri: https://blog.csdn.net</span><br><span class="line">        predicates:</span><br><span class="line">        - Path=/foo/**</span><br><span class="line">        filters:</span><br><span class="line">        - RewritePath=/foo/(?&lt;segment&gt;.*), /$\&#123;segment&#125;</span><br><span class="line">  profiles: rewritepath_route</span><br></pre></td></tr></table></figure>

<p>上面的配置中，所有的<code>/foo/**</code>开始的路径都会命中配置的router，并执行过滤器的逻辑。</p>
<p>在本案例中配置了RewritePath过滤器工厂，此工厂将<code>/foo/(?.*)</code>重写为{segment}，然后转发到<code>https://blog.csdn.net</code>。比如在网页上请求<code>localhost:8081/foo/xxxxxxx</code>，此时会将请求转发到<code>https://blog.csdn.net/xxxxxxx</code>的页面。</p>
<h2 id="自定义过滤器"><a href="#自定义过滤器" class="headerlink" title="自定义过滤器"></a>自定义过滤器</h2><p>Spring Cloud Gateway内置了19种强大的过滤器工厂，能够满足很多场景的需求。在spring Cloud Gateway中，过滤器需要实现GatewayFilter和Ordered这2个接口。写一个RequestTimeFilter，代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">public class RequestTimeFilter implements GatewayFilter, Ordered &#123;</span><br><span class="line"></span><br><span class="line">    private static final Log log = LogFactory.getLog(GatewayFilter.class);</span><br><span class="line">    private static final String REQUEST_TIME_BEGIN = &quot;requestTimeBegin&quot;;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) &#123;</span><br><span class="line"></span><br><span class="line">        exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());</span><br><span class="line">        return chain.filter(exchange).then(</span><br><span class="line">                Mono.fromRunnable(() -&gt; &#123;</span><br><span class="line">                    Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);</span><br><span class="line">                    if (startTime != null) &#123;</span><br><span class="line">                        log.info(exchange.getRequest().getURI().getRawPath() + &quot;: &quot; + (System.currentTimeMillis() - startTime) + &quot;ms&quot;);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;)</span><br><span class="line">        );</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public int getOrder() &#123;</span><br><span class="line">        return 0;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，Ordered中的int getOrder()方法是来给过滤器设定优先级别的，值越大则优先级越低。还有有一个filterI(exchange,chain)方法，在该方法中，先记录了请求的开始时间，并保存在ServerWebExchange中，此处是一个“pre”类型的过滤器，然后再chain.filter的内部类中的run()方法中相当于”post”过滤器，在此处打印了请求所消耗的时间。然后将该过滤器注册到router中，代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">@Bean</span><br><span class="line">public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) &#123;</span><br><span class="line">    // @formatter:off</span><br><span class="line">    return builder.routes()</span><br><span class="line">            .route(r -&gt; r.path(&quot;/customer/**&quot;)</span><br><span class="line">                    .filters(f -&gt; f.filter(new RequestTimeFilter())</span><br><span class="line">                            .addResponseHeader(&quot;X-Response-Default-Foo&quot;, &quot;Default-Bar&quot;))</span><br><span class="line">                    .uri(&quot;http://httpbin.org:80/get&quot;)</span><br><span class="line">                    .order(0)</span><br><span class="line">                    .id(&quot;customer_filter_router&quot;)</span><br><span class="line">            )</span><br><span class="line">            .build();</span><br><span class="line">    // @formatter:on</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>重启程序，通过curl命令模拟请求：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl localhost:8081/customer/123</span><br></pre></td></tr></table></figure>

<p>在程序的控制台输出一下的请求信息的日志：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2018-11-16 15:02:20.177  INFO 20488 --- [ctor-http-nio-3] o.s.cloud.gateway.filter.GatewayFilter   : /customer/123: 152ms</span><br></pre></td></tr></table></figure>



<h3 id="自定义过滤器工厂"><a href="#自定义过滤器工厂" class="headerlink" title="自定义过滤器工厂"></a>自定义过滤器工厂</h3><p>在上面的自定义过滤器中，有没有办法自定义过滤器工厂类呢?这样就可以在配置文件中配置过滤器了。现在需要实现一个过滤器工厂，在打印时间的时候，可以设置参数来决定是否打印请参数。查看GatewayFilterFactory的源码，可以发现GatewayFilterfactory的层级如下：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621081712.png"></p>
<p>过滤器工厂的顶级接口是GatewayFilterFactory，我们可以直接继承它的两个抽象类来简化开发AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory，这两个抽象类的区别就是前者接收一个参数（像StripPrefix和我们创建的这种），后者接收两个参数（像AddResponseHeader）。</p>
<p>过滤器工厂的顶级接口是GatewayFilterFactory，有2个两个较接近具体实现的抽象类，分别为AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory，这2个类前者接收一个参数，比如它的实现类RedirectToGatewayFilterFactory；后者接收2个参数，比如它的实现类AddRequestHeaderGatewayFilterFactory类。现在需要将请求的日志打印出来，需要使用一个参数，这时可以参照RedirectToGatewayFilterFactory的写法。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line">public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory&lt;RequestTimeGatewayFilterFactory.Config&gt; &#123;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    private static final Log log = LogFactory.getLog(GatewayFilter.class);</span><br><span class="line">    private static final String REQUEST_TIME_BEGIN = &quot;requestTimeBegin&quot;;</span><br><span class="line">    private static final String KEY = &quot;withParams&quot;;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public List&lt;String&gt; shortcutFieldOrder() &#123;</span><br><span class="line">        return Arrays.asList(KEY);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    public RequestTimeGatewayFilterFactory() &#123;</span><br><span class="line">        super(Config.class);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public GatewayFilter apply(Config config) &#123;</span><br><span class="line">        return (exchange, chain) -&gt; &#123;</span><br><span class="line">            exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());</span><br><span class="line">            return chain.filter(exchange).then(</span><br><span class="line">                    Mono.fromRunnable(() -&gt; &#123;</span><br><span class="line">                        Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);</span><br><span class="line">                        if (startTime != null) &#123;</span><br><span class="line">                            StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())</span><br><span class="line">                                    .append(&quot;: &quot;)</span><br><span class="line">                                    .append(System.currentTimeMillis() - startTime)</span><br><span class="line">                                    .append(&quot;ms&quot;);</span><br><span class="line">                            if (config.isWithParams()) &#123;</span><br><span class="line">                                sb.append(&quot; params:&quot;).append(exchange.getRequest().getQueryParams());</span><br><span class="line">                            &#125;</span><br><span class="line">                            log.info(sb.toString());</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;)</span><br><span class="line">            );</span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    public static class Config &#123;</span><br><span class="line"></span><br><span class="line">        private boolean withParams;</span><br><span class="line"></span><br><span class="line">        public boolean isWithParams() &#123;</span><br><span class="line">            return withParams;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        public void setWithParams(boolean withParams) &#123;</span><br><span class="line">            this.withParams = withParams;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中 apply(Config config)方法内创建了一个GatewayFilter的匿名类，具体的实现逻辑跟之前一样，只不过加了是否打印请求参数的逻辑，而这个逻辑的开关是config.isWithParams()。静态内部类类Config就是为了接收那个boolean类型的参数服务的，里边的变量名可以随意写，但是要重写List shortcutFieldOrder()这个方法。 。</p>
<p>需要注意的是，在类的构造器中一定要调用下父类的构造器把Config类型传过去，否则会报ClassCastException</p>
<p>最后，需要在工程的启动文件Application类中，向Srping Ioc容器注册RequestTimeGatewayFilterFactory类的Bean。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Bean</span><br><span class="line">public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() &#123;</span><br><span class="line">    return new RequestTimeGatewayFilterFactory();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然后可以在配置文件中配置如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  profiles:</span><br><span class="line">    active: elapse_route</span><br><span class="line"></span><br><span class="line">---</span><br><span class="line">spring:</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      routes:</span><br><span class="line">      - id: elapse_route</span><br><span class="line">        uri: http://httpbin.org:80/get</span><br><span class="line">        filters:</span><br><span class="line">        - RequestTime=false</span><br><span class="line">        predicates:</span><br><span class="line">        - After=2017-01-20T17:42:47.789-07:00[America/Denver]</span><br><span class="line">  profiles: elapse_route</span><br></pre></td></tr></table></figure>

<p>启动工程，在浏览器上访问localhost:8081?name=forezp，可以在控制台上看到，日志输出了请求消耗的时间和请求参数。</p>
<h2 id="global-filter"><a href="#global-filter" class="headerlink" title="global filter"></a>global filter</h2><p>Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter，二者区别如下：</p>
<ul>
<li>GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下，只作用在当前路由上或通过spring.cloud.default-filters配置在全局，作用在所有路由上</li>
<li>GlobalFilter : 全局过滤器，不需要在配置文件中配置，作用在所有的路由上，最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器，它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器，不需要配置，系统初始化时加载，并作用在每个路由上。</li>
</ul>
<p>Spring Cloud Gateway框架内置的GlobalFilter如下：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621081907.png"></p>
<p>上图中每一个GlobalFilter都作用在每一个router上，能够满足大多数的需求。但是如果遇到业务上的定制，可能需要编写满足自己需求的GlobalFilter。在下面的案例中将讲述如何编写自己GlobalFilter，该GlobalFilter会校验请求中是否包含了请求参数“token”，如何不包含请求参数“token”则不转发路由，否则执行正常的逻辑。代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">public class TokenFilter implements GlobalFilter, Ordered &#123;</span><br><span class="line"></span><br><span class="line">    Logger logger=LoggerFactory.getLogger( TokenFilter.class );</span><br><span class="line">    @Override</span><br><span class="line">    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) &#123;</span><br><span class="line">        String token = exchange.getRequest().getQueryParams().getFirst(&quot;token&quot;);</span><br><span class="line">        if (token == null || token.isEmpty()) &#123;</span><br><span class="line">            logger.info( &quot;token is empty...&quot; );</span><br><span class="line">            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);</span><br><span class="line">            return exchange.getResponse().setComplete();</span><br><span class="line">        &#125;</span><br><span class="line">        return chain.filter(exchange);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public int getOrder() &#123;</span><br><span class="line">        return -100;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在上面的TokenFilter需要实现GlobalFilter和Ordered接口，这和实现GatewayFilter很类似。然后根据ServerWebExchange获取ServerHttpRequest，然后根据ServerHttpRequest中是否含有参数token，如果没有则完成请求，终止转发，否则执行正常的逻辑。</p>
<p>然后需要将TokenFilter在工程的启动类中注入到Spring Ioc容器中，代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Bean</span><br><span class="line">public TokenFilter tokenFilter()&#123;</span><br><span class="line">        return new TokenFilter();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>启动工程，使用curl命令请求：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">curl localhost:8081/customer/123</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>可以看到请没有被转发，请求被终止，并在控制台打印了如下日志：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">2018-11-16 15:30:13.543  INFO 19372 --- [ctor-http-nio-2] gateway.TokenFilter                      : token is empty...</span><br></pre></td></tr></table></figure>

<p>上面的日志显示了请求进入了没有传“token”的逻辑。</p>
<h2 id="源码下载-1"><a href="#源码下载-1" class="headerlink" title="源码下载"></a>源码下载</h2><p><a target="_blank" rel="noopener" href="https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-predicate">https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-predicate</a></p>
<h1 id="限流"><a href="#限流" class="headerlink" title="限流"></a>限流</h1><h2 id="常见的限流算法"><a href="#常见的限流算法" class="headerlink" title="常见的限流算法"></a>常见的限流算法</h2><h3 id="计数器算法"><a href="#计数器算法" class="headerlink" title="计数器算法"></a>计数器算法</h3><p>计数器算法采用计数器实现限流有点简单粗暴，一般我们会限制一秒钟的能够通过的请求数，比如限流qps为100，算法的实现思路就是从第一个请求进来开始计时，在接下去的1s内，每来一个请求，就把计数加1，如果累加的数字达到了100，那么后续的请求就会被全部拒绝。等到1s结束后，把计数恢复成0，重新开始计数。具体的实现可以是这样的：对于每次服务调用，可以通过AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值，通过这个最新值和阈值进行比较。这种实现方式，相信大家都知道有一个弊端：如果我在单位时间1s内的前10ms，已经通过了100个请求，那后面的990ms，只能眼巴巴的把请求拒绝，我们把这种现象称为“突刺现象”</p>
<h3 id="漏桶算法"><a href="#漏桶算法" class="headerlink" title="漏桶算法"></a>漏桶算法</h3><p>漏桶算法为了消除”突刺现象”，可以采用漏桶算法实现限流，漏桶算法这个名字就很形象，算法内部有一个容器，类似生活用到的漏斗，当请求进来时，相当于水倒入漏斗，然后从下端小口慢慢匀速的流出。不管上面流量多大，下面流出的速度始终保持不变。不管服务调用方多么不稳定，通过漏桶算法进行限流，每10毫秒处理一次请求。因为处理的速度是固定的，请求进来的速度是未知的，可能突然进来很多请求，没来得及处理的请求就先放在桶里，既然是个桶，肯定是有容量上限，如果桶满了，那么新进来的请求就丢弃。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621084339.png"></p>
<p>在算法实现方面，可以准备一个队列，用来保存请求，另外通过一个线程池（ScheduledExecutorService）来定期从队列中获取请求并执行，可以一次性获取多个并发执行。</p>
<p>这种算法，在使用过后也存在弊端：无法应对短时间的突发流量。</p>
<h3 id="令牌桶算法"><a href="#令牌桶算法" class="headerlink" title="令牌桶算法"></a>令牌桶算法</h3><p>从某种意义上讲，令牌桶算法是对漏桶算法的一种改进，桶算法能够限制请求调用的速率，而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中，存在一个桶，用来存放固定数量的令牌。算法中存在一种机制，以一定的速率往桶中放令牌。每次请求调用需要先获取令牌，只有拿到令牌，才有机会继续执行，否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行，如果桶中令牌数达到上限，就丢弃令牌，所以就存在这种情况，桶中一直有大量的可用令牌，这时进来的请求就可以直接拿到令牌执行，比如设置qps为100，那么限流器初始化完成一秒后，桶中就已经有100个令牌了，这时服务还没完全启动好，等启动完成对外提供服务时，该限流器可以抵挡瞬时的100个请求。所以，只有桶中没有令牌时，请求才会进行等待，最后相当于以一定的速率执行。</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621084405.png"></p>
<p>实现思路：可以准备一个队列，用来保存令牌，另外通过一个线程池定期生成令牌放到队列中，每来一个请求，就从队列中获取一个令牌，并继续执行。</p>
<h2 id="Spring-Cloud-Gateway限流"><a href="#Spring-Cloud-Gateway限流" class="headerlink" title="Spring Cloud Gateway限流"></a>Spring Cloud Gateway限流</h2><p>在Spring Cloud Gateway中，有Filter过滤器，因此可以在“pre”类型的Filter中自行实现上述三种过滤器。但是限流作为网关最基本的功能，Spring Cloud Gateway官方就提供了RequestRateLimiterGatewayFilterFactory这个类，适用Redis和lua脚本实现了令牌桶的方式。</p>
<p>具体实现逻辑在RequestRateLimiterGatewayFilterFactory类中，lua脚本在如下图所示的文件夹中：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621084447.png"></p>
<p>首先在工程的pom文件中引入gateway的起步依赖和redis的reactive依赖，代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> &lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifatId&gt;spring-boot-starter-data-redis-reactive&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>在配置文件中做以下的配置：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: 8081</span><br><span class="line">spring:</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      routes:</span><br><span class="line">      - id: limit_route</span><br><span class="line">        uri: http://httpbin.org:80/get</span><br><span class="line">        predicates:</span><br><span class="line">        - After=2017-01-20T17:42:47.789-07:00[America/Denver]</span><br><span class="line">        filters:</span><br><span class="line">        - name: RequestRateLimiter</span><br><span class="line">          args:</span><br><span class="line">            key-resolver: &#x27;#&#123;@hostAddrKeyResolver&#125;&#x27;</span><br><span class="line">            redis-rate-limiter.replenishRate: 1</span><br><span class="line">            redis-rate-limiter.burstCapacity: 3</span><br><span class="line">  application:</span><br><span class="line">    name: gateway-limiter</span><br><span class="line">  redis:</span><br><span class="line">    host: localhost</span><br><span class="line">    port: 6379</span><br><span class="line">    database: 0</span><br></pre></td></tr></table></figure>

<p>在上面的配置文件，指定程序的端口为8081，配置了 redis的信息，并配置了RequestRateLimiter的限流过滤器，该过滤器需要配置三个参数：</p>
<ul>
<li>burstCapacity，令牌桶总容量。</li>
<li>replenishRate，令牌桶每秒填充平均速率。</li>
<li>key-resolver，用于限流的键的解析器的 Bean 对象的名字。它使用 SpEL 表达式根据#{@beanName}从 Spring 容器中获取 Bean 对象。</li>
</ul>
<p>KeyResolver需要实现resolve方法，比如根据Hostname进行限流，则需要用hostAddress去判断。实现完KeyResolver之后，需要将这个类的Bean注册到Ioc容器中。</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">public class HostAddrKeyResolver implements KeyResolver &#123;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public Mono&lt;String&gt; resolve(ServerWebExchange exchange) &#123;</span><br><span class="line">        return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line">    public HostAddrKeyResolver hostAddrKeyResolver() &#123;</span><br><span class="line">        return new HostAddrKeyResolver();</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>可以根据uri去限流，这时KeyResolver代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">public class UriKeyResolver  implements KeyResolver &#123;</span><br><span class="line"></span><br><span class="line">    @Override</span><br><span class="line">    public Mono&lt;String&gt; resolve(ServerWebExchange exchange) &#123;</span><br><span class="line">        return Mono.just(exchange.getRequest().getURI().getPath());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"> @Bean</span><br><span class="line">    public UriKeyResolver uriKeyResolver() &#123;</span><br><span class="line">        return new UriKeyResolver();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"> </span><br></pre></td></tr></table></figure>

<p>也可以以用户的维度去限流：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@Bean</span><br><span class="line"> KeyResolver userKeyResolver() &#123;</span><br><span class="line">     return exchange -&gt; Mono.just(exchange.getRequest().getQueryParams().getFirst(&quot;user&quot;));</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>用jmeter进行压测，配置10thread去循环请求lcoalhost:8081，循环间隔1s。从压测的结果上看到有部分请求通过，由部分请求失败。通过redis客户端去查看redis中存在的key。如下：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621084558.png"></p>
<p>可见，RequestRateLimiter是使用Redis来进行限流的，并在redis中存储了2个key。关注这两个key含义可以看lua源代码。</p>
<h2 id="源码下载-2"><a href="#源码下载-2" class="headerlink" title="源码下载"></a>源码下载</h2><p><a target="_blank" rel="noopener" href="https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-limiter">https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-limiter</a></p>
<h1 id="配合注册中心进行路由转发"><a href="#配合注册中心进行路由转发" class="headerlink" title="配合注册中心进行路由转发"></a>配合注册中心进行路由转发</h1><p>之前采用硬编码的方式进行路由转发，现在Spring Cloud Gateway如何配合服务注册中心进行路由转发。</p>
<h2 id="工程介绍"><a href="#工程介绍" class="headerlink" title="工程介绍"></a>工程介绍</h2><p>本案例中使用spring boot的版本为2.0.3.RELEASE,spring cloud版本为Finchley.RELEASE。在中涉及到了三个工程， 分别为注册中心eureka-server、服务提供者service-hi、 服务网关service-gateway，如下：</p>
<table>
<thead>
<tr>
<th align="left">工程名</th>
<th align="left">端口</th>
<th align="left">作用</th>
</tr>
</thead>
<tbody><tr>
<td align="left">eureka-server</td>
<td align="left">8761</td>
<td align="left">注册中心eureka server</td>
</tr>
<tr>
<td align="left">service-hi</td>
<td align="left">8762</td>
<td align="left">服务提供者 eurka client</td>
</tr>
<tr>
<td align="left">service-gateway</td>
<td align="left">8081</td>
<td align="left">路由网关 eureka client</td>
</tr>
</tbody></table>
<p>这三个工程中，其中service-hi、service-gateway向注册中心eureka-server注册。用户的请求首先经过service-gateway，根据路径由gateway的predict 去断言进到哪一个 router， router经过各种过滤器处理后，最后路由到具体的业务服务，比如 service-hi。如图：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621085026.png"></p>
<p>eureka-server、service-hi这两个工程直接复制于我的另外一篇文章<a target="_blank" rel="noopener" href="https://blog.csdn.net/forezp/article/details/81040925">https://blog.csdn.net/forezp/article/details/81040925</a> ，在这就不在重复，可以查看源码，源码地址见文末链接。 其中，service-hi服务对外暴露了一个RESTFUL接口“/hi”接口。现在重点讲解service-gateway。</p>
<h2 id="gateway工程详细介绍"><a href="#gateway工程详细介绍" class="headerlink" title="gateway工程详细介绍"></a>gateway工程详细介绍</h2><p>在gateway工程中引入项目所需的依赖，包括eureka-client的起步依赖和gateway的起步依赖，代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-netflix-eureka-client&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br></pre></td></tr></table></figure>

<p>在工程的配置文件application.yml中 ，指定程序的启动端口为8081，注册地址、gateway的配置等信息，配置信息如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: 8081</span><br><span class="line"></span><br><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: sc-gateway-service</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      discovery:</span><br><span class="line">        locator:</span><br><span class="line">          enabled: true</span><br><span class="line">          lowerCaseServiceId: true</span><br><span class="line">          </span><br><span class="line">eureka:</span><br><span class="line">  client:</span><br><span class="line">    service-url:</span><br><span class="line">      defaultZone: http://localhost:8761/eureka/</span><br></pre></td></tr></table></figure>

<p>其中，spring.cloud.gateway.discovery.locator.enabled为true，表明gateway开启服务注册和发现的功能，并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router，这个router将以服务名开头的请求路径转发到对应的服务。spring.cloud.gateway.discovery.locator.lowerCaseServiceId是将请求路径上的服务名配置为小写（因为服务注册的时候，向注册中心注册时将服务名转成大写的了），比如以/service-hi/*的请求路径被路由转发到服务名为service-hi的服务上。</p>
<p>在浏览器上请求输入localhost:8081/service-hi/hi?name=1323，网页获取以下的响应：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hi 1323 ,i am from port:8762</span><br></pre></td></tr></table></figure>

<p>在上面的例子中，向gateway-service发送的请求时，url必须带上服务名service-hi这个前缀，才能转发到service-hi上，转发之前会将service-hi去掉。 那么我能不能自定义请求路径呢，毕竟根据服务名有时过于太长，或者历史的原因不能根据服务名去路由，需要由自定义路径并转发到具体的服务上。答案是肯定的是可以的，只需要修改工程的配置文件application.yml，具体配置如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: sc-gateway-server</span><br><span class="line">  cloud:</span><br><span class="line">    gateway:</span><br><span class="line">      discovery:</span><br><span class="line">        locator:</span><br><span class="line">          enabled: false</span><br><span class="line">          lowerCaseServiceId: true</span><br><span class="line">      routes:</span><br><span class="line">      - id: service-hi</span><br><span class="line">        uri: lb://SERVICE-HI</span><br><span class="line">        predicates:</span><br><span class="line">          - Path=/demo/**</span><br><span class="line">        filters:</span><br><span class="line">          - StripPrefix=1</span><br><span class="line">         </span><br></pre></td></tr></table></figure>

<p>在上面的配置中，配置了一个Path 的predict,将以/demo/**开头的请求都会转发到uri为lb://SERVICE-HI的地址上，lb://SERVICE-HI即service-hi服务的负载均衡地址，并用StripPrefix的filter 在转发之前将/demo去掉。同时将spring.cloud.gateway.discovery.locator.enabled改为false，如果不改的话，之前的localhost:8081/service-hi/hi?name=1323这样的请求地址也能正常访问，因为这时为每个服务创建了2个router。</p>
<p>在浏览器上请求localhost:8081/demo/hi?name=1323，浏览器返回以下的响应：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hi 1323 ,i am from port:8762</span><br></pre></td></tr></table></figure>

<p>返回的结果跟我们预想的一样。</p>
<h2 id="源码下载-3"><a href="#源码下载-3" class="headerlink" title="源码下载"></a>源码下载</h2><p><a target="_blank" rel="noopener" href="https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-cloud">https://github.com/forezp/SpringCloudLearning/tree/master/sc-f-gateway-cloud</a></p>
<h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h1><h2 id="工程构建"><a href="#工程构建" class="headerlink" title="工程构建"></a>工程构建</h2><p>新建一个gateway的工程，工程目录如下：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621073916.png"></p>
<p>gateway需要注册到nacos中去，需要引入以下的依赖：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-alibaba-nacos-discovery&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-gateway&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-boot-starter-webflux&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br><span class="line">&lt;dependency&gt;</span><br><span class="line">    &lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;</span><br><span class="line">    &lt;artifactId&gt;spring-cloud-starter-loadbalancer&lt;/artifactId&gt;</span><br><span class="line">&lt;/dependency&gt;</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>在配置文件application.pom文件：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">server:</span><br><span class="line">  port: 5000</span><br><span class="line"></span><br><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: gateway</span><br><span class="line">  cloud:</span><br><span class="line">    nacos:</span><br><span class="line">      discovery:</span><br><span class="line">        server-addr: 127.0.0.1:8848</span><br><span class="line">    gateway:</span><br><span class="line">      discovery:</span><br><span class="line">        locator:</span><br><span class="line">          enabled: false</span><br><span class="line">          lowerCaseServiceId: true</span><br><span class="line">      routes:</span><br><span class="line">        - id: provider</span><br><span class="line">          uri: lb://provider</span><br><span class="line">          predicates:</span><br><span class="line">            - Path=/provider/**</span><br><span class="line">          filters:</span><br><span class="line">            - StripPrefix=1</span><br><span class="line">        - id: consumer</span><br><span class="line">          uri: lb://consumer</span><br><span class="line">          predicates:</span><br><span class="line">            - Path=/consumer/**</span><br><span class="line">          filters:</span><br><span class="line">            - StripPrefix=1</span><br></pre></td></tr></table></figure>

<p>配置的解释请阅读文末的相关教程，在这里不再重复。</p>
<p>在工程的启动文件加上相关注解：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> @SpringBootApplication</span><br><span class="line">@EnableDiscoveryClient</span><br><span class="line">public class GatewayApplication &#123;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        SpringApplication.run(GatewayApplication.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>依次启动gateway\consumer\provider三个工程，在nacos中已经成功注册：</p>
<p><img src="https://gitee.com/chenyy-2017/pic/raw/master/note/20210621073946.png"></p>
<p>在浏览器上输入<a target="_blank" rel="noopener" href="http://localhost:5000/consumer/hi-feign%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E8%BF%94%E5%9B%9E%E5%93%8D%E5%BA%94%EF%BC%9A">http://localhost:5000/consumer/hi-feign，浏览器返回响应：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hello feign, i&#x27;m provider ,my port:8762</span><br></pre></td></tr></table></figure>



<h2 id="创建一个简单的路由"><a href="#创建一个简单的路由" class="headerlink" title="创建一个简单的路由"></a>创建一个简单的路由</h2><p>在spring cloud gateway中使用RouteLocator的Bean进行路由转发，将请求进行处理，最后转发到目标的下游服务。在本案例中，会将请求转发到<a href="http://httpbin.org:80这个地址上。代码如下：">http://httpbin.org:80这个地址上。代码如下：</a></p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">@SpringBootApplication</span><br><span class="line">@RestController</span><br><span class="line">public class Application &#123;</span><br><span class="line"></span><br><span class="line">    public static void main(String[] args) &#123;</span><br><span class="line">        SpringApplication.run(Application.class, args);</span><br><span class="line">    &#125;</span><br><span class="line">    @Bean</span><br><span class="line">    public RouteLocator myRoutes(RouteLocatorBuilder builder) &#123;</span><br><span class="line">       return builder.routes()</span><br><span class="line">        .route(p -&gt; p</span><br><span class="line">            .path(&quot;/get&quot;)</span><br><span class="line">            .filters(f -&gt; f.addRequestHeader(&quot;Hello&quot;, &quot;World&quot;))</span><br><span class="line">            .uri(&quot;http://httpbin.org:80&quot;))</span><br><span class="line">        .build();</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure>

<p>在上面的myRoutes方法中，使用了一个RouteLocatorBuilder的bean去创建路由，除了创建路由RouteLocatorBuilder可以让你添加各种<strong>predicates</strong>和<strong>filters</strong>，predicates断言的意思，顾名思义就是根据具体的请求的规则，由具体的route去处理，filters是各种过滤器，用来对请求做各种判断和修改。</p>
<p>上面创建的route可以让请求“/get”请求都转发到“<a target="_blank" rel="noopener" href="http://httpbin.org/get%E2%80%9D%E3%80%82%E5%9C%A8route%E9%85%8D%E7%BD%AE%E4%B8%8A%EF%BC%8C%E6%88%91%E4%BB%AC%E6%B7%BB%E5%8A%A0%E4%BA%86%E4%B8%80%E4%B8%AAfilter%EF%BC%8C%E8%AF%A5filter%E4%BC%9A%E5%B0%86%E8%AF%B7%E6%B1%82%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AAheader,key%E4%B8%BAhello%EF%BC%8Cvalue%E4%B8%BAworld%E3%80%82">http://httpbin.org/get”。在route配置上，我们添加了一个filter，该filter会将请求添加一个header,key为hello，value为world。</a></p>
<p>启动springboot项目，在浏览器上<a target="_blank" rel="noopener" href="http://localhost:8080/get%EF%BC%8C%E6%B5%8F%E8%A7%88%E5%99%A8%E6%98%BE%E7%A4%BA%E5%A6%82%E4%B8%8B">http://localhost:8080/get，浏览器显示如下</a>:</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  &quot;args&quot;: &#123;&#125;, </span><br><span class="line">  &quot;headers&quot;: &#123;</span><br><span class="line">    &quot;Accept&quot;: &quot;text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8&quot;, </span><br><span class="line">    &quot;Accept-Encoding&quot;: &quot;gzip, deflate, br&quot;, </span><br><span class="line">    &quot;Accept-Language&quot;: &quot;zh-CN,zh;q=0.9,en;q=0.8&quot;, </span><br><span class="line">    &quot;Cache-Control&quot;: &quot;max-age=0&quot;, </span><br><span class="line">    &quot;Connection&quot;: &quot;close&quot;, </span><br><span class="line">    &quot;Cookie&quot;: &quot;_ga=GA1.1.412536205.1526967566; JSESSIONID.667921df=node01oc1cdl4mcjdx1mku2ef1l440q1.node0; screenResolution=1920x1200&quot;, </span><br><span class="line">    &quot;Forwarded&quot;: &quot;proto=http;host=\&quot;localhost:8080\&quot;;for=\&quot;0:0:0:0:0:0:0:1:60036\&quot;&quot;, </span><br><span class="line">    &quot;Hello&quot;: &quot;World&quot;, </span><br><span class="line">    &quot;Host&quot;: &quot;httpbin.org&quot;, </span><br><span class="line">    &quot;Upgrade-Insecure-Requests&quot;: &quot;1&quot;, </span><br><span class="line">    &quot;User-Agent&quot;: &quot;Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36&quot;, </span><br><span class="line">    &quot;X-Forwarded-Host&quot;: &quot;localhost:8080&quot;</span><br><span class="line">  &#125;, </span><br><span class="line">  &quot;origin&quot;: &quot;0:0:0:0:0:0:0:1, 210.22.21.66&quot;, </span><br><span class="line">  &quot;url&quot;: &quot;http://localhost:8080/get&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可见当我们向gateway工程请求“/get”,gateway会将工程的请求转发到“<a target="_blank" rel="noopener" href="http://httpbin.org/get%E2%80%9D%EF%BC%8C%E5%B9%B6%E4%B8%94%E5%9C%A8%E8%BD%AC%E5%8F%91%E4%B9%8B%E5%89%8D%EF%BC%8C%E5%8A%A0%E4%B8%8A%E4%B8%80%E4%B8%AAfilter%EF%BC%8C%E8%AF%A5filter%E4%BC%9A%E5%B0%86%E8%AF%B7%E6%B1%82%E6%B7%BB%E5%8A%A0%E4%B8%80%E4%B8%AAheader,key%E4%B8%BAhello%EF%BC%8Cvalue%E4%B8%BAworld%E3%80%82">http://httpbin.org/get”，并且在转发之前，加上一个filter，该filter会将请求添加一个header,key为hello，value为world。</a></p>
<p>注意HTTPBin展示了请求的header hello和值world。</p>
<h2 id="使用Hystrix"><a href="#使用Hystrix" class="headerlink" title="使用Hystrix"></a>使用Hystrix</h2><p>在spring cloud gateway中可以使用Hystrix。Hystrix是 spring cloud中一个服务熔断降级的组件，在微服务系统有着十分重要的作用。 Hystrix是 spring cloud gateway中是以filter的形式使用的，代码如下：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">@Bean</span><br><span class="line"> public RouteLocator myRoutes(RouteLocatorBuilder builder) &#123;</span><br><span class="line">     String httpUri = &quot;http://httpbin.org:80&quot;;</span><br><span class="line">     return builder.routes()</span><br><span class="line">         .route(p -&gt; p</span><br><span class="line">             .path(&quot;/get&quot;)</span><br><span class="line">             .filters(f -&gt; f.addRequestHeader(&quot;Hello&quot;, &quot;World&quot;))</span><br><span class="line">             .uri(httpUri))</span><br><span class="line">         .route(p -&gt; p</span><br><span class="line">             .host(&quot;*.hystrix.com&quot;)</span><br><span class="line">             .filters(f -&gt; f</span><br><span class="line">                 .hystrix(config -&gt; config</span><br><span class="line">                     .setName(&quot;mycmd&quot;)</span><br><span class="line">                     .setFallbackUri(&quot;forward:/fallback&quot;)))</span><br><span class="line">             .uri(httpUri))</span><br><span class="line">         .build();</span><br><span class="line"> &#125;</span><br></pre></td></tr></table></figure>

<p>在上面的代码中，我们使用了另外一个router，该router使用host去断言请求是否进入该路由，当请求的host有“*.hystrix.com”，都会进入该router，该router中有一个hystrix的filter,该filter可以配置名称、和指向性fallback的逻辑的地址，比如本案例中重定向到了“/fallback”。</p>
<p>现在写的一个“/fallback”的l逻辑：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">@RequestMapping(&quot;/fallback&quot;)</span><br><span class="line">   public Mono&lt;String&gt; fallback() &#123;</span><br><span class="line">       return Mono.just(&quot;fallback&quot;);</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure>

<p>Mono是一个Reactive stream，对外输出一个“fallback”字符串。</p>
<p>使用curl执行以下命令：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl --dump-header - --header &#x27;Host: www.hystrix.com&#x27; http://localhost:8080/delay/3</span><br></pre></td></tr></table></figure>

<p>返回的响应为：</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">fallback</span><br></pre></td></tr></table></figure>

<p>可见，带host<a target="_blank" rel="noopener" href="http://www.hystrix.com的请求执行了hystrix的fallback的逻辑./">www.hystrix.com的请求执行了hystrix的fallback的逻辑。</a></p>
<h2 id="源码下载-4"><a href="#源码下载-4" class="headerlink" title="源码下载"></a>源码下载</h2><p><a target="_blank" rel="noopener" href="https://github.com/forezp/SpringCloudLearning/tree/master/sc-2020-chapter2">https://github.com/forezp/SpringCloudLearning/tree/master/sc-2020-chapter2</a></p>
<p>参考：</p>
<ul>
<li>Spring Cloud Gateway 初体验：<a target="_blank" rel="noopener" href="https://www.fangzhipeng.com/springcloud/2018/11/06/sc-f-gateway1.html">https://www.fangzhipeng.com/springcloud/2018/11/06/sc-f-gateway1.html</a></li>
<li>Spring Cloud Gateway 之Predict篇：<a target="_blank" rel="noopener" href="https://www.fangzhipeng.com/springcloud/2018/12/05/sc-f-gateway2.html">https://www.fangzhipeng.com/springcloud/2018/12/05/sc-f-gateway2.html</a></li>
<li>spring cloud gateway之filter篇：<a target="_blank" rel="noopener" href="https://www.fangzhipeng.com/springcloud/2018/12/21/sc-f-gatway3.html">https://www.fangzhipeng.com/springcloud/2018/12/21/sc-f-gatway3.html</a></li>
<li>spring cloud gateway 之限流篇：<a target="_blank" rel="noopener" href="https://www.fangzhipeng.com/springcloud/2018/12/22/sc-f-gatway4.html">https://www.fangzhipeng.com/springcloud/2018/12/22/sc-f-gatway4.html</a></li>
<li>spring cloud gateway之服务注册与发现：<a target="_blank" rel="noopener" href="https://www.fangzhipeng.com/springcloud/2018/12/23/sc-f-gateway5.html">https://www.fangzhipeng.com/springcloud/2018/12/23/sc-f-gateway5.html</a></li>
<li>使用spring cloud gateway作为服务网关：<a target="_blank" rel="noopener" href="https://www.fangzhipeng.com/springcloud/2021/04/03/sc-2020-gateway.html">https://www.fangzhipeng.com/springcloud/2021/04/03/sc-2020-gateway.html</a></li>
<li>Spring Cloud官方案例：<a target="_blank" rel="noopener" href="https://spring.io/guides/gs/gateway">https://spring.io/guides/gs/gateway</a> </li>
</ul>
 
      <!-- reward -->
      
      <div id="reword-out">
        <div id="reward-btn">
          打赏
        </div>
      </div>
      
    </div>
    

    <!-- copyright -->
    
    <div class="declare">
      <ul class="post-copyright">
        <li>
          <i class="ri-copyright-line"></i>
          <strong>版权声明： </strong>
          
          本博客所有文章除特别声明外，著作权归作者所有。转载请注明出处！
          
        </li>
      </ul>
    </div>
    
    <footer class="article-footer">
       
<div class="share-btn">
      <span class="share-sns share-outer">
        <i class="ri-share-forward-line"></i>
        分享
      </span>
      <div class="share-wrap">
        <i class="arrow"></i>
        <div class="share-icons">
          
          <a class="weibo share-sns" href="javascript:;" data-type="weibo">
            <i class="ri-weibo-fill"></i>
          </a>
          <a class="weixin share-sns wxFab" href="javascript:;" data-type="weixin">
            <i class="ri-wechat-fill"></i>
          </a>
          <a class="qq share-sns" href="javascript:;" data-type="qq">
            <i class="ri-qq-fill"></i>
          </a>
          <a class="douban share-sns" href="javascript:;" data-type="douban">
            <i class="ri-douban-line"></i>
          </a>
          <!-- <a class="qzone share-sns" href="javascript:;" data-type="qzone">
            <i class="icon icon-qzone"></i>
          </a> -->
          
          <a class="facebook share-sns" href="javascript:;" data-type="facebook">
            <i class="ri-facebook-circle-fill"></i>
          </a>
          <a class="twitter share-sns" href="javascript:;" data-type="twitter">
            <i class="ri-twitter-fill"></i>
          </a>
          <a class="google share-sns" href="javascript:;" data-type="google">
            <i class="ri-google-fill"></i>
          </a>
        </div>
      </div>
</div>

<div class="wx-share-modal">
    <a class="modal-close" href="javascript:;"><i class="ri-close-circle-line"></i></a>
    <p>扫一扫，分享到微信</p>
    <div class="wx-qrcode">
      <img src="//api.qrserver.com/v1/create-qr-code/?size=150x150&data=http://example.com/2021/06/21/framework/Spring%20Cloud%20Gateway/" alt="微信分享二维码">
    </div>
</div>

<div id="share-mask"></div>  
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/Spring-Cloud/" rel="tag">Spring Cloud</a></li></ul>

    </footer>
  </div>

   
  <nav class="article-nav">
    
      <a href="/2021/06/21/network/%E9%99%90%E6%B5%81%E7%AE%97%E6%B3%95/" class="article-nav-link">
        <strong class="article-nav-caption">上一篇</strong>
        <div class="article-nav-title">
          
            限流算法
          
        </div>
      </a>
    
    
      <a href="/2021/06/20/framework/Docker%E5%9F%BA%E6%9C%AC%E5%91%BD%E4%BB%A4/" class="article-nav-link">
        <strong class="article-nav-caption">下一篇</strong>
        <div class="article-nav-title">Docker基本命令</div>
      </a>
    
  </nav>

   
<!-- valine评论 -->
<div id="vcomments-box">
  <div id="vcomments"></div>
</div>
<script src="//cdn1.lncld.net/static/js/3.0.4/av-min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/valine@1.4.14/dist/Valine.min.js"></script>
<script>
  new Valine({
    el: "#vcomments",
    app_id: "",
    app_key: "",
    path: window.location.pathname,
    avatar: "monsterid",
    placeholder: "给我的文章加点评论吧~",
    recordIP: true,
  });
  const infoEle = document.querySelector("#vcomments .info");
  if (infoEle && infoEle.childNodes && infoEle.childNodes.length > 0) {
    infoEle.childNodes.forEach(function (item) {
      item.parentNode.removeChild(item);
    });
  }
</script>
<style>
  #vcomments-box {
    padding: 5px 30px;
  }

  @media screen and (max-width: 800px) {
    #vcomments-box {
      padding: 5px 0px;
    }
  }

  #vcomments-box #vcomments {
    background-color: #fff;
  }

  .v .vlist .vcard .vh {
    padding-right: 20px;
  }

  .v .vlist .vcard {
    padding-left: 10px;
  }
</style>

 
   
     
</article>

</section>
      <footer class="footer">
  <div class="outer">
    <ul>
      <li>
        Copyrights &copy;
        2021
        <i class="ri-heart-fill heart_icon"></i> Chenyy
      </li>
    </ul>
    <ul>
      <li>
        
      </li>
    </ul>
    <ul>
      <li>
        
        
        <span>
  <span><i class="ri-user-3-fill"></i>访问人数:<span id="busuanzi_value_site_uv"></span></s>
  <span class="division">|</span>
  <span><i class="ri-eye-fill"></i>浏览次数:<span id="busuanzi_value_page_pv"></span></span>
</span>
        
      </li>
    </ul>
    <ul>
      
    </ul>
    <ul>
      
    </ul>
    <ul>
      <li>
        <!-- cnzz统计 -->
        
        <script type="text/javascript" src='https://s9.cnzz.com/z_stat.php?id=1278069914&amp;web_id=1278069914'></script>
        
      </li>
    </ul>
  </div>
</footer>
      <div class="float_btns">
        <div class="totop" id="totop">
  <i class="ri-arrow-up-line"></i>
</div>

<div class="todark" id="todark">
  <i class="ri-moon-line"></i>
</div>

      </div>
    </main>
    <aside class="sidebar on">
      <button class="navbar-toggle"></button>
<nav class="navbar">
  
  <div class="logo">
    <a href="/"><img src="/images/ayer-side.svg" alt="ChenyyのBlog"></a>
  </div>
  
  <ul class="nav nav-main">
    
    <li class="nav-item">
      <a class="nav-item-link" href="/">主页</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/archives">文章</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/categories">分类</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags">标签</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/tags/book">收藏</a>
    </li>
    
    <li class="nav-item">
      <a class="nav-item-link" href="/about/me">关于我</a>
    </li>
    
  </ul>
</nav>
<nav class="navbar navbar-bottom">
  <ul class="nav">
    <li class="nav-item">
      
      <a class="nav-item-link nav-item-search"  title="搜索">
        <i class="ri-search-line"></i>
      </a>
      
      
      <a class="nav-item-link" target="_blank" href="/atom.xml" title="RSS Feed">
        <i class="ri-rss-line"></i>
      </a>
      
    </li>
  </ul>
</nav>
<div class="search-form-wrap">
  <div class="local-search local-search-plugin">
  <input type="search" id="local-search-input" class="local-search-input" placeholder="Search...">
  <div id="local-search-result" class="local-search-result"></div>
</div>
</div>
    </aside>
    <script>
      if (window.matchMedia("(max-width: 768px)").matches) {
        document.querySelector('.content').classList.remove('on');
        document.querySelector('.sidebar').classList.remove('on');
      }
    </script>
    <div id="mask"></div>

<!-- #reward -->
<div id="reward">
  <span class="close"><i class="ri-close-line"></i></span>
  <p class="reward-p"><i class="ri-cup-line"></i>请我喝杯咖啡吧~</p>
  <div class="reward-box">
    
    
  </div>
</div>
    
<script src="/js/jquery-2.0.3.min.js"></script>


<script src="/js/lazyload.min.js"></script>

<!-- Tocbot -->


<script src="/js/tocbot.min.js"></script>

<script>
  tocbot.init({
    tocSelector: '.tocbot',
    contentSelector: '.article-entry',
    headingSelector: 'h1, h2, h3, h4, h5, h6',
    hasInnerContainers: true,
    scrollSmooth: true,
    scrollContainer: 'main',
    positionFixedSelector: '.tocbot',
    positionFixedClass: 'is-position-fixed',
    fixedSidebarOffset: 'auto'
  });
</script>

<script src="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jquery-modal@0.9.2/jquery.modal.min.css">
<script src="https://cdn.jsdelivr.net/npm/justifiedGallery@3.7.0/dist/js/jquery.justifiedGallery.min.js"></script>

<script src="/dist/main.js"></script>

<!-- ImageViewer -->

<!-- Root element of PhotoSwipe. Must have class pswp. -->
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">

    <!-- Background of PhotoSwipe. 
         It's a separate element as animating opacity is faster than rgba(). -->
    <div class="pswp__bg"></div>

    <!-- Slides wrapper with overflow:hidden. -->
    <div class="pswp__scroll-wrap">

        <!-- Container that holds slides. 
            PhotoSwipe keeps only 3 of them in the DOM to save memory.
            Don't modify these 3 pswp__item elements, data is added later on. -->
        <div class="pswp__container">
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
            <div class="pswp__item"></div>
        </div>

        <!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
        <div class="pswp__ui pswp__ui--hidden">

            <div class="pswp__top-bar">

                <!--  Controls are self-explanatory. Order can be changed. -->

                <div class="pswp__counter"></div>

                <button class="pswp__button pswp__button--close" title="Close (Esc)"></button>

                <button class="pswp__button pswp__button--share" style="display:none" title="Share"></button>

                <button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>

                <button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>

                <!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
                <!-- element will get class pswp__preloader--active when preloader is running -->
                <div class="pswp__preloader">
                    <div class="pswp__preloader__icn">
                        <div class="pswp__preloader__cut">
                            <div class="pswp__preloader__donut"></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
                <div class="pswp__share-tooltip"></div>
            </div>

            <button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
            </button>

            <button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)">
            </button>

            <div class="pswp__caption">
                <div class="pswp__caption__center"></div>
            </div>

        </div>

    </div>

</div>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css">
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js"></script>

<script>
    function viewer_init() {
        let pswpElement = document.querySelectorAll('.pswp')[0];
        let $imgArr = document.querySelectorAll(('.article-entry img:not(.reward-img)'))

        $imgArr.forEach(($em, i) => {
            $em.onclick = () => {
                // slider展开状态
                // todo: 这样不好，后面改成状态
                if (document.querySelector('.left-col.show')) return
                let items = []
                $imgArr.forEach(($em2, i2) => {
                    let img = $em2.getAttribute('data-idx', i2)
                    let src = $em2.getAttribute('data-target') || $em2.getAttribute('src')
                    let title = $em2.getAttribute('alt')
                    // 获得原图尺寸
                    const image = new Image()
                    image.src = src
                    items.push({
                        src: src,
                        w: image.width || $em2.width,
                        h: image.height || $em2.height,
                        title: title
                    })
                })
                var gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, {
                    index: parseInt(i)
                });
                gallery.init()
            }
        })
    }
    viewer_init()
</script>

<!-- MathJax -->

<!-- Katex -->

<!-- busuanzi  -->


<script src="/js/busuanzi-2.3.pure.min.js"></script>


<!-- ClickLove -->

<!-- ClickBoom1 -->

<!-- ClickBoom2 -->

<!-- CodeCopy -->


<link rel="stylesheet" href="/css/clipboard.css">

<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  function wait(callback, seconds) {
    var timelag = null;
    timelag = window.setTimeout(callback, seconds);
  }
  !function (e, t, a) {
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '<i class="ri-file-copy-2-line"></i><span>COPY</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      $(".article pre code").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });
      clipboard.on('success', function(e) {
        let $btn = $(e.trigger);
        $btn.addClass('copied');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-checkbox-circle-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPIED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-checkbox-circle-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
      clipboard.on('error', function(e) {
        e.clearSelection();
        let $btn = $(e.trigger);
        $btn.addClass('copy-failed');
        let $icon = $($btn.find('i'));
        $icon.removeClass('ri-file-copy-2-line');
        $icon.addClass('ri-time-line');
        let $span = $($btn.find('span'));
        $span[0].innerText = 'COPY FAILED';
        
        wait(function () { // 等待两秒钟后恢复
          $icon.removeClass('ri-time-line');
          $icon.addClass('ri-file-copy-2-line');
          $span[0].innerText = 'COPY';
        }, 2000);
      });
    }
    initCopyCode();
  }(window, document);
</script>


<!-- CanvasBackground -->


    
  </div>
</body>

</html>